SPB Git forge

spb/countryatlas

Public
20commits 1branches 0releases
268.3 MBsize
maindefault branch
12 days agolast push
TypeScript 57% Python 38.6% JavaScript 3.6% CSS 0.6%
2.7 KB · 50 lines tsx
Raw Blame History
1import { ImageResponse } from 'next/og';2import { t } from '@/i18n';3import { api, safe } from '@/lib/api';4import { displayValue } from '@/lib/format';5import { OG_INK2, OG_INK3, OG_RULE, OG_SIZE_H, OG_SIZE_W, OgFrame, OgWordmark } from '../../og-shared';67export const alt = 'Country statistics on CountryAtlas';8export const size = { width: OG_SIZE_W, height: OG_SIZE_H };9export const contentType = 'image/png';10export const revalidate = 3600;1112/** Per-country social image: flag + name + three headline values (population, GDP per capita, life expectancy). */13export default async function CountryOgImage({ params }: { params: Promise<{ slug: string }> }) {14  const { slug } = await params;15  const data = await safe(api.country(slug));16  const c = data?.country;17  const name = c?.name ?? slug.replace(/-/g, ' ');18  const pick = (id: string) => data?.headline.find((m) => m.indicator === id && m.has_data) ?? null;19  const facts = [pick('population'), pick('gdp-per-capita'), pick('life-expectancy')].filter((m): m is NonNullable<typeof m> => !!m).slice(0, 3);20  const sub = [c?.capital, c?.region_name, c?.income_name].filter(Boolean).join(' · ');21  return new ImageResponse(22    (23      <OgFrame>24        <OgWordmark size={28} />25        <div style={{ display: 'flex', alignItems: 'center', gap: 28, marginTop: 56 }}>26          <div style={{ fontSize: 120, lineHeight: 1, display: 'flex' }}>{c?.flag ?? ''}</div>27          <div style={{ display: 'flex', flexDirection: 'column' }}>28            <div style={{ fontSize: 68, fontWeight: 600, letterSpacing: -1.5, lineHeight: 1.05 }}>{name}</div>29            {sub ? <div style={{ marginTop: 10, fontSize: 24, color: OG_INK2 }}>{sub}</div> : null}30          </div>31        </div>32        <div style={{ display: 'flex', gap: 48, marginTop: 'auto', paddingTop: 24, borderTop: `1px solid ${OG_RULE}` }}>33          {facts.length === 0 ? (34            <div style={{ fontSize: 24, color: OG_INK2 }}>{t('country.description', { name }).split(':')[1]?.trim() ?? ''}</div>35          ) : null}36          {facts.map((m) => (37            <div key={m.indicator} style={{ display: 'flex', flexDirection: 'column' }}>38              <div style={{ fontSize: 18, color: OG_INK3, textTransform: 'uppercase', letterSpacing: 1.5 }}>{m.indicator_name ?? m.indicator}</div>39              <div style={{ fontSize: 44, fontWeight: 600, marginTop: 6 }}>{displayValue(m.value, m, m.formatted)}</div>40              <div style={{ fontSize: 18, color: OG_INK3, marginTop: 2 }}>{String(m.year ?? '')}</div>41            </div>42          ))}43        </div>44        <div style={{ position: 'absolute', right: 64, bottom: 24, fontSize: 20, color: OG_INK3 }}>{t('og.site')}</div>45      </OgFrame>46    ),47    { ...size },48  );49}50